package runload

import (
	"fmt"
	"strings"
	"sync"
	"time"

	. "gitee.com/kingzyt/common/behaviorTree"
	"gitee.com/kingzyt/common/log"
	. "gitee.com/kingzyt/common/util"
)

type BaseRobot struct {
	*BaseBTRuntimeData

	mutex           sync.Mutex
	id              int
	uid             int64
	uidStr          string
	proxiedUserName string
	pnameMutex      sync.RWMutex
	actionsState    map[string]NodeState

	statMutex   sync.RWMutex
	stat2Mutex  sync.RWMutex
	actionsStat map[string]*ActionStat

	updateCnt AtomicInt64

	OnAbort func()
}

func NewBaseRobot(id int, proxiedUserName string) *BaseRobot {
	rlt := &BaseRobot{
		BaseBTRuntimeData: NewBaseBTRuntimeData(fmt.Sprintf("r%d", id)),
		id:                id,
		proxiedUserName:   proxiedUserName,
		actionsState:      map[string]NodeState{},
		actionsStat:       map[string]*ActionStat{},
	}

	return rlt
}

// id is const after doing NewBaseRobot, so there is no lock for it
func (self *BaseRobot) GetID() int {
	return self.id
}

func (self *BaseRobot) Lock() {
	self.mutex.Lock()
}
func (self *BaseRobot) Unlock() {
	self.mutex.Unlock()
}

func (self *BaseRobot) Update(dtNs int64) {
	self.updateCnt.Add(1)
}

// called by bt.update which is protected by robot lock in runloader /////////////////////////////////////////
// for bt /////
func (self *BaseRobot) ActionRunningCheck(actid string) NodeState {
	state, ok := self.actionsState[actid]
	if !ok {
		return INVALID
	}
	return state
}
func (self *BaseRobot) GetRunningTimeoutT(runningNodeName string, runningNodeType NodeType) int64 {
	switch runningNodeType {
	case NodeType_ActionWait:
		return int64(time.Hour * 999)
	case NodeType_Action:
		return int64(time.Second * 30)
	default:
		return int64(time.Second * 3)
	}
}

// called by subclass robot /////
func (self *BaseRobot) SetUid(uid int64) {
	self.uid = uid
	self.uidStr = fmt.Sprintf("%d", uid)
}
func (self *BaseRobot) GetUid() int64 {
	return self.uid
}
func (self *BaseRobot) GetUidStr() string {
	return self.uidStr
}
func (self *BaseRobot) UserName() string {
	return self.GetProxiedUserName()
}
func (self *BaseRobot) ShowLog(format string, args ...interface{}) {
	log.Info(self.UserName(), log.I, format, args...)
}
func (self *BaseRobot) ShowErr(format string, args ...interface{}) {
	log.Error(self.UserName(), format, args...)
}

func (self *BaseRobot) Abort(reason string) {
	if self.OnAbort != nil {
		self.OnAbort()
	}
	for actionId := range self.actionsState {
		self.actionsState[actionId] = FAILURE
	}

	self.BaseBTRuntimeData.Abort(reason)

	if !strings.Contains(reason, "change proxied user to") {
		self.ShowErr("abort [%s]", reason)
	}
}
func (self *BaseRobot) ChangeActionState(actionId string, state NodeState) {
	self.actionsState[actionId] = state
}

func (self *BaseRobot) AddReqStat(actid string) {
	self.statMutex.Lock()
	defer self.statMutex.Unlock()
	stat, ok2 := self.actionsStat[actid]
	if !ok2 {
		stat = NewActionStat(actid, &self.stat2Mutex)
		self.actionsStat[actid] = stat
	}
	stat.AddReq()
}
func (self *BaseRobot) AddFatalErrStat(actid string) {
	self.statMutex.Lock()
	defer self.statMutex.Unlock()
	stat, ok := self.actionsStat[actid]
	if ok {
		stat.AddFatalErr()
	}
}
func (self *BaseRobot) AddResStat(actid string) {
	self.statMutex.Lock()
	defer self.statMutex.Unlock()
	stat, ok2 := self.actionsStat[actid]
	if !ok2 {
		stat = NewActionStat(actid, &self.stat2Mutex)
		self.actionsStat[actid] = stat
	}
	stat.AddRes()
}

/////////////////////////////////////////
func (self *BaseRobot) ChangeProxiedUserName(name string) string {
	self.Lock()
	defer self.Unlock()
	self.pnameMutex.Lock()
	defer self.pnameMutex.Unlock()

	oldName := self.proxiedUserName

	self.Abort("change proxied user to: " + name)
	self.proxiedUserName = name
	return oldName
}
func (self *BaseRobot) GetProxiedUserName() string {
	self.pnameMutex.RLock()
	defer self.pnameMutex.RUnlock()
	return self.proxiedUserName
}

func (self *BaseRobot) GetCurRunningNodeName() string {
	self.Lock()
	defer self.Unlock()
	return self.BaseBTRuntimeData.GetCurRunningNodeName()
}

func (self *BaseRobot) GetActionStats(proc func(stats map[string]*ActionStat)) {
	if proc == nil {
		return
	}

	self.statMutex.RLock()
	defer self.statMutex.RUnlock()
	proc(self.actionsStat)
}
func (self *BaseRobot) GetUpdateCnt() int64 {
	return self.updateCnt.Get()
}
